Gate the two-file agent-notes contract, with the matcher derived by counting this tree - #42
Merged
Merged
Conversation
…ounting this tree The 2026-08-04 split made every anchor between CLAUDE.md and documentation/agent-notes.md load-bearing, and nothing checked them. scripts/check-agent-notes.ts verifies the narrative file is tracked, that no section is emptied down to its heading (a container whose body is its subsections is exempt and counted), and that every pointer at it resolves. It runs from test/scripts/agent-notes.test.ts, so it rides the required ci / verify contexts and prepublishOnly rather than adding a fourth workflow. THE MATCHER WAS DERIVED BY COUNTING BOTH SPELLINGS HERE, NOT PORTED. Porting without re-counting has produced a false green twice: a qualified-only matcher would have covered 3 of ncpdp's 38 pointers, and would have matched nothing at all in terminology (42 bare, zero qualified) while still exiting 0. Measured on this tree: 11 qualified pointers, all in CLAUDE.md, and zero bare pointers. So this gate matches the qualified form only, and a bare-form census refuses the run at exit 2 if that spelling ever appears, because the evidence the scope rests on would have gone stale. No count is written into the gate's prose as a promise; the OK line prints them all on every run. PROVEN NON-VACUOUS AGAINST A CLONE OF THE REAL TREE before it was believed: a misspelled real anchor and an emptied real section each exit 1; a deleted narrative file exits 1 with the pair and every pointer; a bare pointer that RESOLVES exits 2; a neutered matcher and a tree with every pointer rewritten out of range both exit 2 rather than going green. Re-derived for this repository rather than inherited: the exit codes come from scripts/phi-scan.ts, and an unmerged path REFUSES here even though phi-scan leaves that status unenumerated, because its reasoning (git commit refuses an unmerged index) covers a staged route this gate does not have. The NUL skip is a disclosed miss and is required rather than tidy: the tree tracks vendored tarballs and a synthetic DICOM fixture. A draft claimed that skip differs from check-no-emdash.sh's partition; that was false and was corrected by reading that gate's own OK line. Both key on an actual NUL byte. CLAUDE.md sat at its byte budget, so the branch-protection, PHI-residual and em-dash blocks were compressed to their imperatives and the reasoning each one compresses (already present in the narrative file) now carries it. No trap was deleted and no ceiling was raised. Two verify.sh steps are red and BOTH REPRODUCE ON THE BASE 0a1b322: pnpm audit (advisories in @modelcontextprotocol/sdk transitive deps) and the licenses gate (a local pnpm store index error for the vendored fhir tarball). Neither is touched by this slice, which adds no dependency.
Pass 1 returned REFUTED with three INTRODUCED major findings. None needed a bigger matcher; all three were a claim that was wrong or a hole left open. 1. THE BARE CENSUS WAS SCOPED TO THE PAIR ON A FALSE JUSTIFICATION. The prose claimed a tree-wide census would refuse on CHANGELOG.md's pull-request references, on quoted shebangs in tsup.config.ts and docs-content/, and on a quoted comment marker in scripts/phi-allow-list.txt. Running it tree-wide proves none of them can: a shebang, a `# synthetic` marker and a lone hash are all outside the anchor class, and the CHANGELOG spans are digits-only, which the census classifies as references by design. The only files that could refuse were this gate's own source and its own test, which wrote bare spans out literally while illustrating the rule. So the narrow scope was a self-exemption for the two files where a broken pointer would hide, which the gate's own CONTRACT_BASENAME note already forbids. The samples are now assembled from parts, as the qualified ones always were, and the census covers every opened file. That also closes the real hole: a bare pointer in a THIRD file was covered by neither the matcher nor a pair-scoped census. 2. "A RENAME" WAS IN THE OPENING PROMISE AND IS NOT CAUGHT. Both halves match on basename, which is deliberate and is what lets a qualified, a ./-relative and a bare-path pointer reach one target, but the directory is never compared: moving the file to docs/agent-notes.md while pointers keep their prefix exits 0 while every rendered link 404s. The promise is narrowed to "stops being tracked at all" and the miss is disclosed and pinned. The guard was not grown. 3. A HEADING INSIDE AN HTML COMMENT MINTS A PHANTOM ANCHOR, and the disclosed-miss list omitted it. Every sibling copy of this gate discloses it; this one forgot, and the omission was the defect. Now disclosed and pinned in the false-green direction. Also from that pass: the written-down count of sibling repos lacking the file was already wrong (eight, not seven) and the set moves whenever a submodule is added, so the number is dropped in favour of the class, including in the stderr a human reads. Two [PINNED] misses had no case in the block whose own invariant promises one; both now do, alongside cases for the two misses added here. The stale "twenty real headings" figure is gone for the same reason. CLAUDE.md gained the basename trap as a one-line imperative and stays under its ceiling; the attw block was compressed to its imperatives, with the reasoning it compresses already in the archive. 53 tests pass. Every local gate green.
…'s cost
Pass 2 returned REFUTED: the fix for an overclaim had shipped a new one.
1. "EVERY SIBLING COPY DISCLOSES THE HTML-COMMENT MISS" IS FALSE, and I took the
phrasing from the review rather than from the tree. Re-measured on this
checkout: ncpdp, terminology and astm disclose it; mllp, ccda, transform and
docs do not, anywhere. mllp decides it, because this gate's disclosed-miss
block is transcribed from mllp's shape and that list stops at (ix) as well, so
the shared ancestor never carried the entry and "this one forgot" is backwards.
The claim is withdrawn in the script, the archive and the changelog, and what
replaces it is the enumeration rather than a count, so it can self-correct.
Recorded with it: the umbrella's conventions assert the universal ("in every
copy including ccda's") and the repos do not bear that out. Four sibling repos
are owed this disclosure. That is the umbrella's to reconcile.
2. THE WIDENED CENSUS REFUSES ON AN ANCHOR-SHAPED SPAN THAT WAS NEVER A POINTER,
and nothing disclosed it. A hex colour or a CSS id in an inline code span reds
at exit 2 with advice to re-derive the matcher, which is wrong for a colour.
Now disclosed as miss (xii) and pinned. DISCLOSED, NOT NARROWED: the direction
is conservative (refuse, never a false green), and the pair-scoped census this
replaced avoided the cost only by leaving every third file uncovered by both
the matcher and the census.
3. The list read (viii), (ix), (vi-b), (xi), (x). Reordered so (vi-b) follows the
(vi) it cross-references and (xi), (xii) follow (x).
A draft of (xii) wrote its two examples out literally and the gate refused on its
own source at exit 2, which is the widened census working exactly as intended.
Both examples are now described rather than written, for the same reason every
sample in these two files is assembled from parts. Swept: no non-digit literal
bare span survives in any of the five files this slice touches.
54 tests pass. Every local gate green. CLAUDE.md unchanged and under its ceiling.
Third generation of one defect, and the last: a correction carrying its own
unmeasured claim about other repositories.
1. "FOUR SIBLING REPOS ARE OWED THIS DISCLOSURE" IS WRONG. Only three are.
mllp, ccda and transform derive an anchor by SLUGGING a heading, so a
commented-out heading mints a phantom anchor there and the HTML-comment
disclosure applies to each. docs does not slug at all: it resolves pointers
against explicit <a id="..."></a> anchors, so the failure mode cannot arise
there, and writing the disclosure into it would name a failure that repo
cannot have. That is exactly the trap mllp's own miss (vi) states: a
disclosure that names the wrong failure mode sends the next reader hunting
something that cannot happen.
The durable lesson is recorded with it, because "count more carefully" is not
it: A CLAIM ABOUT ANOTHER REPOSITORY IS NOT CHECKABLE FROM INSIDE THIS ONE, so
it is measured against that repository's source or it is not made. Three
review passes were spent on three generations of that one mistake.
2. Miss (xii) overstated its own cost. A hex colour refuses only when it carries
a LETTER; one whose digits are all decimal is classified as a pull-request
reference and exits 0. Corrected, and the direction is noted: an entry that
overstates its own cost is the safe way round to be wrong.
3. The changelog carried a bare count of a moving set and a dangling comparison
("whose list stops at the same entry", with no numeral anywhere in the entry).
Both replaced by the enumeration, which is the rule this slice already adopted
twice: keep the list or drop the number.
No gate behaviour changed. scripts/check-agent-notes.ts is comment-only in this
diff. 54 tests pass, every local gate green, CLAUDE.md untouched and under its
ceiling.
Both are wording, neither changes gate behaviour, and pass 4 returned PASS with them filed rather than blocking. Fixed anyway, because leaving a known-false sentence inside a slice whose whole subject is overclaiming would be perverse. 1. The archive said a bullet is "three levels deep". Measured, it is two, and a sibling of the correction above it rather than its child. Clause deleted; a pure deletion cannot introduce a new claim. 2. Miss (xii) said a span refuses "only when it carries a letter". Exact for the hex-colour half, over-narrow for the CSS-id half: an id of digits with an underscore or a hyphen carries no letter and still refuses. The boundary is exactly DIGITS_ONLY, so it is now stated as "not all decimal digits", with both wrong versions named so neither is written again. Every version of this boundary was fail-closed, so none was ever a false green. 484 tests pass. Every local gate green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The 2026-08-04 split made every anchor between
CLAUDE.mdanddocumentation/agent-notes.mdload-bearing, and nothing checked them.
scripts/check-agent-notes.tsnow verifies that thenarrative file is tracked, that no section is emptied down to its heading (a container whose body is
its subsections is exempt and counted), and that every pointer at it resolves.
It runs from
test/scripts/agent-notes.test.ts, so it rides the requiredci / verifycontexts andprepublishOnlyrather than adding a fourth workflow. In this packageprepublishOnlyis a realpre-publish gate, so the check blocks a publish too.
The matcher was derived by counting this tree, not ported
Porting a sibling's matcher without re-counting has produced a false green twice in this ecosystem: a
qualified-only matcher would have covered 3 of
ncpdp's 38 pointers, and would have matched nothingat all in
terminology(42 bare, zero qualified) while still exiting 0.Measured here before the matcher was written: 11 qualified pointers, all in
CLAUDE.md, and zerobare pointers. The bare-shaped spans in the pair are 5 digits-only pull-request references plus
spans that are not anchor-shaped (a quoted shebang, quoted headings). So this gate matches the
qualified form only, and a bare-form census refuses the run at exit 2 if a genuine bare pointer
ever appears, because the evidence the scope rests on would have gone stale. No count is written into
the gate's prose as a promise; the OK line prints them all on every run.
Proven non-vacuous before it was believed
Every control below was run against a clone of the real tree, not a fixture:
CLAUDE.mdmisspelledThe last two are the
terminologyscenario. A gate that went green on either would be worthlesswhile looking healthy.
Re-derived for this repository rather than inherited
scripts/phi-scan.ts:0,1a finding,2a refusal.phi-scanleaves that status unenumerated. Itsreasoning (
git commitrefuses an unmerged index at exit 128) covers a staged route this gatedoes not have.
@cosyte/*tarballs and a synthetic DICOM fixture. A draft claimed this partition differs from
check-no-emdash.sh's; that was false and was corrected by reading that gate's own OK line.Both key on an actual NUL byte. Git's binary set is the wider one, which is why neither gate may be
reduced to
grep -I.headings, not by copying a sibling's table.
Budget
CLAUDE.mdwas at its ceiling, so the branch-protection, PHI-residual and em-dash blocks werecompressed to their imperatives; the reasoning each compresses was already in the narrative file, and
the new gate's own narrative was added there. No trap was deleted and no ceiling was raised.
Gate status, stated plainly
scripts/verify.sh cliis red on exactly two steps and both reproduce on the base0a1b322:pnpm audit --prod(advisories in@modelcontextprotocol/sdktransitive deps) and the licenses gate(a local pnpm store index error for the vendored
fhirtarball). This slice adds no dependency andtouches neither. Every other rung is green: typecheck, lint, format:check, phi-scan, no-emdash,
no-internal-refs, test:coverage, build, attw, smoke.
verify.sh's ladder does not yet know the namecheck:agent-notes, and says so itself, so a greenverify.shmeans less than a green CI here. The checks on this PR are the authority.